/**
 * import jsprintorbitor.bridge2native.Bridge2graphSprintExperiment.java
 */
#include "Bridge2graphSprintExperiment.h"

#include <assert.h>
#include <exception>
#include <iostream>
#include <string.h>

#include <boost/lexical_cast.hpp>

#include "graph_support/attributes/LengthEdgeAttributes.h"
#include "graph_support/impls/vector/VectorGraph.h"
#include "graph_support/impls/compact/CompactGraph.h"
#include "graph_support/impls/matrix/MatrixGraph.h"
#include "graph_support/impls/wrap_igraph/WrapIGraph.h"

#include "graph_support/exceptions/GraphOperationException.h"

#include "BridgeUtils.h"

#include "BFSGraphTraversalAlgorithm.h"
#include "BFSOnCompactGraph.h"
#include "DejkstraAlgorithm.h"
#include "DinicAlgorithm.h"
#include "MinCostFlowAlgorithm.h"
#include "EasyRandomGraphGenerator.h"
#include "WrapGraphInVirtualGenerator.h"
#include "CompactGraphGenerator.h"
#include "FlowGraphGenerator.h"
#include "CostFlowGraphGenerator.h"
#include "ChainGraphAlgorithm.h"

#include "Bridge2nativePackageNameAdapter.h"

using namespace native_orbitor;
using namespace GRAPH_SUPPORT_NS;
using namespace GRAPH_SUPPORT_NS::impls;
using namespace GRAPH_SUPPORT_NS::impls::vector;
using namespace GRAPH_SUPPORT_NS::impls::matrix;
using namespace GRAPH_SUPPORT_NS::impls::compact;
using namespace GRAPH_SUPPORT_NS::impls::wrap_igraph;
using namespace GRAPH_SUPPORT_NS::utils;

typedef boost::shared_ptr< GraphPointerGuard > NoTypeGraphExchangePtr;
typedef boost::shared_ptr< IGraphGenerator > GraphGeneratorExchangePtr;
typedef boost::shared_ptr< IGraphAlgorithm > GraphAlgorithmExchangePtr;

namespace
{
	bool checkValidObjectHolderOutput( JNIEnv * env, jintArray output )
	{
		if ( ! output )
		{
			jthrow( env, "IllegalArgumentException", "Object holder output array was empty" );
			return false;
		}
		int length = env->GetArrayLength( output );
		if ( length != 2 )
		{
			jthrow( env, "IllegalArgumentException",
					std::string( "Object holder output array was expected to be equal to 2 but was: " ) +
					boost::lexical_cast< std::string >( length ) );
			return false;
		}
		return true;
	}
}

// TODO: 2think: generator works with virtual

BRIDGE_2_NATIVE_METHOD( newEasyRandomGraphGenerator )
  ( JNIEnv * env, jclass, jstring jsgraphType, jintArray output )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	const std::string graphType = jstr2stdstr( env, jsgraphType );
	if ( graphType == "VECTOR" )
	{
		typedef VectorGraphTmpl< int > Graph;
		createObjectAndDeleteFn< EasyRandomGraphGenerator< Graph >, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "MATRIX" )
	{
		typedef MatrixGraphTmpl< int > Graph;
		createObjectAndDeleteFn< EasyRandomGraphGenerator< Graph >, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "IGRAPH" )
	{
		typedef VectorGraphTmpl< int > Graph;
		createObjectAndDeleteFn< WrapGraphInVirtualGenerator< EasyRandomGraphGenerator< Graph > >, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "COMPACT" )
	{
		typedef CompactGraphTmpl< int > Graph;
		createObjectAndDeleteFn< EasyRandomGraphGenerator< Graph >, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "COMPACT_NOTSTRUCTED" )
	{
		typedef VectorGraphTmpl< int > Graph;
		createObjectAndDeleteFn< CompactGraphGenerator< EasyRandomGraphGenerator< Graph > >, GraphGeneratorExchangePtr >( env, output );
	} else
	{
		jthrow( env, "IllegalArgumentException", std::string( "Bad graph type: " ) + graphType );
	}
}

BRIDGE_2_NATIVE_METHOD( newFlowGraphGenerator )
  ( JNIEnv * env, jclass, jstring jsgraphType, jintArray output )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	const std::string graphType = jstr2stdstr( env, jsgraphType );
	if ( graphType == "VECTOR" ) {
		typedef VectorGraphTmpl< int > Graph;
		createObjectAndDeleteFn< FlowGraphGenerator/*< Graph >*/, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "MATRIX" ) {
		jthrow( env, "UnsupportedOperationException", "Flow graph generation 4 MATRIX wasn't implemented" );
//		typedef MatrixGraphTmpl< int > Graph;
//		createObjectAndDeleteFn< EasyRandomGraphGenerator< Graph >, GraphGeneratorExchangePtr >( env, output );
	} else {
		jthrow( env, "IllegalArgumentException", std::string( "Bad graph type: " ) + graphType );
	}
}

BRIDGE_2_NATIVE_METHOD( newCostFlowGraphGenerator )
  ( JNIEnv * env, jclass, jstring jsgraphType, jintArray output )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	const std::string graphType = jstr2stdstr( env, jsgraphType );
	if ( graphType == "VECTOR" ) {
		typedef VectorGraphTmpl< int > Graph;
		createObjectAndDeleteFn< CostFlowGraphGenerator/*< Graph >*/, GraphGeneratorExchangePtr >( env, output );
//		createObjectAndDeleteFn< FlowGraphGenerator/*< Graph >*/, GraphGeneratorExchangePtr >( env, output );
	} else
	if ( graphType == "MATRIX" ) {
		jthrow( env, "UnsupportedOperationException", "Flow graph generation 4 MATRIX wasn't implemented" );
//		typedef MatrixGraphTmpl< int > Graph;
//		createObjectAndDeleteFn< EasyRandomGraphGenerator< Graph >, GraphGeneratorExchangePtr >( env, output );
	} else {
		jthrow( env, "IllegalArgumentException", std::string( "Bad graph type: " ) + graphType );
	}
}

BRIDGE_2_NATIVE_METHOD( newBFSGraphTraversalAlgorithm )
  ( JNIEnv * env, jclass, jintArray output )
{
	// TODO: graph name convention
	typedef VectorGraphTmpl< int > VGraph;
	typedef MatrixGraphTmpl< int > MGraph;
	typedef WrapIGraph< attributes::Length1EdgeAttributes > IGraph;
	typedef CompactGraphTmpl< int > CGraph;

	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	GraphAlgorithmExchangePtr chainAlgPtr =
			createObjectAndDeleteFn< ChainGraphAlgorithm, GraphAlgorithmExchangePtr >( env, output );

	ChainGraphAlgorithm * chainAlg = dynamic_cast< ChainGraphAlgorithm * >( chainAlgPtr.get() );
	assert( chainAlg );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< VGraph >() ) );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< MGraph >() ) );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< IGraph >() ) );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new BFSOnCompactGraph() ) );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< CGraph >() ) );
}

BRIDGE_2_NATIVE_METHOD( newDejkstraAlgorithm )
  ( JNIEnv * env, jclass, jintArray output, jint startVert )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	typedef VectorGraphTmpl< int > VGraph;
	typedef MatrixGraphTmpl< int > MGraph;

	GraphAlgorithmExchangePtr chainAlgPtr =
			createObjectAndDeleteFn< ChainGraphAlgorithm, GraphAlgorithmExchangePtr >( env, output );

	ChainGraphAlgorithm * chainAlg = dynamic_cast< ChainGraphAlgorithm * >( chainAlgPtr.get() );
	assert( chainAlg );
	size_t startVertId = startVert;
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new DejkstraAlgorithm< VGraph >( startVertId ) ) );
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new DejkstraAlgorithm< MGraph >( startVertId ) ) );
}

BRIDGE_2_NATIVE_METHOD( newDinicAlgorithm )
  ( JNIEnv * env, jclass, jintArray output, jint jsrc, jint jdest )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	typedef VectorGraphTmpl< int > VGraph;
	typedef MatrixGraphTmpl< int > MGraph;

	GraphAlgorithmExchangePtr chainAlgPtr =
			createObjectAndDeleteFn< ChainGraphAlgorithm, GraphAlgorithmExchangePtr >( env, output );

	ChainGraphAlgorithm * chainAlg = dynamic_cast< ChainGraphAlgorithm * >( chainAlgPtr.get() );
	assert( chainAlg );
	size_t src = jsrc;
	size_t dest = jdest;
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new DinicAlgorithm/*< VGraph >*/( src, dest ) ) );
}

BRIDGE_2_NATIVE_METHOD( newMinCostFlowAlgorithm )
  ( JNIEnv * env, jclass, jintArray output, jint jsrc, jint jdest )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}

	typedef VectorGraphTmpl< int > VGraph;
	typedef MatrixGraphTmpl< int > MGraph;

	GraphAlgorithmExchangePtr chainAlgPtr =
			createObjectAndDeleteFn< ChainGraphAlgorithm, GraphAlgorithmExchangePtr >( env, output );

	ChainGraphAlgorithm * chainAlg = dynamic_cast< ChainGraphAlgorithm * >( chainAlgPtr.get() );
	assert( chainAlg );
	size_t src = jsrc;
	size_t dest = jdest;
	chainAlg->pushbackSubalgorithm( ChainGraphAlgorithm::AlgPtr( new MinCostFlowAlgorithm/*< VGraph >*/( src, dest ) ) );
}

BRIDGE_2_NATIVE_METHOD( generateGraph )
  ( JNIEnv * env, jclass, jint graphGeneratorInt, jobject grGenSettings, jintArray output )
{
	if ( ! checkValidObjectHolderOutput( env, output ) )
	{
		return;
	}
	if ( graphGeneratorInt == 0 )
	{
		jthrow( env, "NullPointerException", "Graph generator was null" );
		return;
	}
	if ( ! grGenSettings )
	{
		jthrow( env, "NullPointerException", "Graph generation settings was null" );
		return;
	}

	stdext::hash_map< std::string, double, Hash4StdString > grGenSettingsMap =
			parseGenSettingsMap( env, grGenSettings );

	GraphGeneratorExchangePtr * graphGeneratorPtr =
			reinterpret_cast< GraphGeneratorExchangePtr * >( graphGeneratorInt );
	IGraphGenerator & graphGenerator = * * graphGeneratorPtr;

	std::auto_ptr< GraphPointerGuard > graphGuardPtr;
	try
	{
		graphGuardPtr = graphGenerator.generateGraph( grGenSettingsMap );
	} catch ( const exceptions::GraphOperationException & exc )
	{
		jthrow( env, "IllegalArgumentException", std::string( "Graph generation fault: " ) + exc.getMessage() );
		return;
	} catch ( ... )
	{
		jthrow( env, "IllegalArgumentException", std::string( "Graph generation fault: unknown error" ) );
		return;
	}

	std::auto_ptr< NoTypeGraphExchangePtr > graphPtrWrap(
			new NoTypeGraphExchangePtr( graphGuardPtr.get() ) );
	graphGuardPtr.release();
	int graphPtrInt = reinterpret_cast< int >( graphPtrWrap.get() );

	void ( * deleteGraphFunc ) ( int ) = & ( deleteMe< boost::shared_ptr< VectorGraphTmpl< int > > > );
	int deleteGraphFuncInt = reinterpret_cast< int >( deleteGraphFunc );

	int result[] = { graphPtrInt, deleteGraphFuncInt };
	env->SetIntArrayRegion( output, 0, 2, result );

	graphPtrWrap.release();
}

BRIDGE_2_NATIVE_METHOD( runGraphAlgorithm )
  ( JNIEnv * env, jclass, jint algorithmInt, jint graphInt )
{
	if ( algorithmInt == 0 )
	{
		jthrow( env, "NullPointerException", "Algorithm was null" );
		return;
	}
	if ( graphInt == 0 )
	{
		jthrow( env, "NullPointerException", "Algorithm was null" );
		return;
	}

	GraphAlgorithmExchangePtr * algorithmPtr =
			reinterpret_cast< GraphAlgorithmExchangePtr * >( algorithmInt );
	IGraphAlgorithm & algorithm = * * algorithmPtr;

	NoTypeGraphExchangePtr * graphGuardPtr =
			reinterpret_cast< NoTypeGraphExchangePtr * >( graphInt );
	GraphPointerGuard & graphGuard = * * graphGuardPtr;

	if ( algorithm.isApplicable2graph( graphGuard.getAbstractGraphPtr() ) )
	{
		algorithm.runOnGraph( graphGuard.getAbstractGraphPtr() );
	} else
	{
		jthrow( env, "IllegalArgumentException", "Algorithm doesn't accepts such type of graph" );
		return;
	}
}

BRIDGE_2_NATIVE_METHOD( deleteObject )
  ( JNIEnv * env, jclass, jint objectInt, jint deleteMeFuncInt )
{
	if ( deleteMeFuncInt == 0 )
	{
		jthrow( env, "NullPointerException", "Delete function pointer was null" );
		return;
	}
	try
	{
		void ( * deleteMeFunc ) ( int ) = reinterpret_cast< void (*) ( int ) >( deleteMeFuncInt );
		deleteMeFunc( objectInt );
	} catch ( const exceptions::GraphOperationException & exc )
	{
		jthrow( env, "IllegalArgumentException", std::string( "Remove graph fault: " ) + exc.getMessage() );
		return;
	} catch ( ... )
	{
		jthrow( env, "IllegalArgumentException", std::string( "Remove graph fault: unknown error" ) );
		return;
	}
}
